Skip to content

Compile negated conditions as inverted jumps - #23292

Open
staabm wants to merge 1 commit into
php:masterfrom
staabm:negated-jumps
Open

Compile negated conditions as inverted jumps#23292
staabm wants to merge 1 commit into
php:masterfrom
staabm:negated-jumps

Conversation

@staabm

@staabm staabm commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

disclaimer: this change was generated by claude opus. I have little experience with php-src development


'if (!$x)', while/do-while conditions and ternaries with a top-level '!' emitted a BOOL_NOT into a temporary followed by JMPZ/JMPNZ - two dispatches and a TMP per evaluation, since without the opcache optimizer nothing rewrites it. Strip '!' layers in the condition and flip the jump opcode instead; both paths evaluate the operand with i_zend_is_true, so behavior including undefined-variable warnings is identical. This also lets comparisons under '!' fuse with the jump (smart branch), which BOOL_NOT previously prevented.

Adjusts two opcode-dump tests for the changed temporary numbering.


note the improved *_negated variants

after PR

➜  php-src git:(negated-jumps) ✗ sapi/cli/php -n -d opcache.enable_cli=0 negated_conditions_repro.php 
PHP 8.6.0-dev
ifIters=25000000 outer=80000 inner=64 rounds=5

case                best(s)     avg(s)      sink
------------------------------------------------------
if_positive         0.157475    0.158927    0
if_negated          0.156479    0.159483    0
ternary_positive    0.170012    0.171106    0
ternary_negated     0.169497    0.171230    0
while_positive      0.016812    0.017440    0
while_negated       0.016239    0.016372    0
do_while_positive   0.016911    0.017201    0
do_while_negated    0.016190    0.016443    0

negated/positive ratios (best time, lower is better):
if         0.9937x
ternary    0.9970x
while      0.9659x
do_while   0.9574x

before PR

➜  php-src git:(negated-jumps) ✗ sapi/cli/php_old -n -d opcache.enable_cli=0 negated_conditions_repro.php                                             
PHP 8.6.0-dev
ifIters=25000000 outer=80000 inner=64 rounds=5

case                best(s)     avg(s)      sink
------------------------------------------------------
if_positive         0.155232    0.156882    0
if_negated          0.166127    0.169627    0
ternary_positive    0.167289    0.168908    0
ternary_negated     0.178253    0.178780    0
while_positive      0.017045    0.017106    0
while_negated       0.020980    0.021447    0
do_while_positive   0.016768    0.016951    0
do_while_negated    0.020996    0.021222    0

negated/positive ratios (best time, lower is better):
if         1.0702x
ternary    1.0655x
while      1.2308x
do_while   1.2521x

using negated_conditions_repro.php

'if (!$x)', while/do-while conditions and ternaries with a top-level
'!' emitted a BOOL_NOT into a temporary followed by JMPZ/JMPNZ - two
dispatches and a TMP per evaluation, since without the opcache
optimizer nothing rewrites it. Strip '!' layers in the condition and
flip the jump opcode instead; both paths evaluate the operand with
i_zend_is_true, so behavior including undefined-variable warnings is
identical. This also lets comparisons under '!' fuse with the jump
(smart branch), which BOOL_NOT previously prevented.

Adjusts two opcode-dump tests for the changed temporary numbering.
@staabm
staabm requested a review from dstogov as a code owner August 15, 2026 11:23
staabm referenced this pull request in ondrejmirtes/php-src Aug 15, 2026
'if (!$x)', while/do-while conditions and ternaries with a top-level
'!' emitted a BOOL_NOT into a temporary followed by JMPZ/JMPNZ - two
dispatches and a TMP per evaluation, since without the opcache
optimizer nothing rewrites it. Strip '!' layers in the condition and
flip the jump opcode instead; both paths evaluate the operand with
i_zend_is_true, so behavior including undefined-variable warnings is
identical. This also lets comparisons under '!' fuse with the jump
(smart branch), which BOOL_NOT previously prevented.

Adjusts two opcode-dump tests for the changed temporary numbering.
@Girgias
Girgias requested review from arnaud-lb and iluuu1994 August 15, 2026 12:04
@TimWolla

Copy link
Copy Markdown
Member

since without the opcache optimizer nothing rewrites it

I'm not excited about increasing the complexity of the compiler for something that is already handled by the Optimizer.

@iluuu1994

iluuu1994 commented Aug 18, 2026

Copy link
Copy Markdown
Member

Same. It's probably possible to introduce a setting that compiles files with optimizations enabled for applications where it makes sense, even when opcache is disabled. Then, PHPStan can enable that setting in its root file. WDYT?

@Girgias

Girgias commented Aug 19, 2026

Copy link
Copy Markdown
Member

Same. It's probably possible to introduce a setting that compiles files with optimizations enabled for applications where it makes sense, even when opcache is disabled. Then, PHPStan can enable that setting in its root file. WDYT?

Is there a reason to not run certain optimizations? I know this wasn't done as the optimizer used to be part of opcache, but now that it's not (and opcache is always bundled) it might make sense to just enable some unconditionally?

@iluuu1994

Copy link
Copy Markdown
Member

It's more that optimizations take time, and while scripts in shm will run many times, scripts without shm will only run once. So the cost of running optimizations may exceed the cost of the script itself, especially if most code paths don't execute (e.g. imagine a Symfony command).

But I don't know about concrete number and whether this line of reasoning actually makes sense.

@Girgias

Girgias commented Aug 19, 2026

Copy link
Copy Markdown
Member

It's more that optimizations take time, and while scripts in shm will run many times, scripts without shm will only run once. So the cost of running optimizations may exceed the cost of the script itself, especially if most code paths don't execute (e.g. imagine a Symfony command).

But I don't know about concrete number and whether this line of reasoning actually makes sense.

I guess this is something that might be worth exploring, but I think this requires to fully untie the Optimizer from opcache.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants